package cn.com.idmy.cloud.service.impl;

import cn.com.idmy.base.TreeNode;
import cn.com.idmy.base.util.Assert;
import cn.com.idmy.base.util.TreeUtil;
import cn.com.idmy.cloud.service.DefaultTreeNodeService;
import cn.com.idmy.orm.core.OrmDao;
import cn.com.idmy.orm.core.Query;
import lombok.NonNull;
import org.dromara.hutool.core.collection.CollUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;
import java.util.Objects;

import static cn.com.idmy.base.util.TreeUtil.rootId;
import static cn.com.idmy.base.util.TreeUtil.treeNameSeparator;


public abstract class DefaultTreeNodeServiceImpl<D extends OrmDao<T, Long>, T extends TreeNode<T>> extends DefaultServiceImpl<D, T, Long> implements DefaultTreeNodeService<T> {
    @Override
    public @NotNull List<T> all() {
        return list(q().orderBy(TreeNode::getSort));
    }

    @Transactional
    @Override
    public void fixTree() {
        var all = list(q().select(TreeNode::getId, TreeNode::getPid, TreeNode::getName, TreeNode::getLevel, TreeNode::getSort));
        var ts = TreeUtil.listToTrees(all);
        TreeUtil.recursiveTree(ts);
        super.update(all);
    }

    protected void fixTree(@NonNull T t) {
        var pid = t.getPid();
        if (Objects.equals(pid, rootId)) {
            t.setLevel(0);
            t.setIdsPath(String.valueOf(t.getId()));
            t.setNamesPath(t.getName());
        } else {
            var parent = super.get(q().eq(pid).select(TreeNode::getTreeId, TreeNode::getIdsPath, TreeNode::getNamesPath, TreeNode::getLevel, TreeNode::getIsLeaf));
            t.setTreeId(parent.getTreeId());
            t.setIdsPath(TreeUtil.joinPath(parent.getIdsPath(), t.getId()));
            t.setNamesPath(TreeUtil.joinPath(parent.getNamesPath(), t.getName()));
            t.setLevel(parent.getLevel() + 1);
            if (parent.getIsLeaf()) {
                parent.setIsLeaf(false);
                super.update(parent);
            }
        }
    }

    protected void fixTree(@NonNull T t, @NonNull String namesPath) {
        var ls = listDescendants(t.getId(), q().select(TreeNode::getId, TreeNode::getNamesPath));
        for (int i = 0, size = ls.size(); i < size; i++) {
            var descendant = ls.get(i);
            var tmp = descendant.getNamesPath().replaceFirst(namesPath, t.getNamesPath());
            descendant.setNamesPath(tmp);
        }
        super.update(ls);
    }

    @Override
    public boolean create(@NonNull T t) {
        t.setId(newId());
        String title = dao.table().title();
        Assert.notBlank(t.getName(), "【】名称不能为空", title);
        Assert.isFalse(t.getName().contains(treeNameSeparator), "【】名称中不能有【{}】符号", title, treeNameSeparator);
        t.setIsLeaf(true);
        fixTree(t);
        super.create(t);
        var children = t.getChildren();
        if (CollUtil.isNotEmpty(children)) {
            for (int i = 0, size = children.size(); i < size; i++) {
                var child = children.get(i);
                child.setPid(t.getId());
                create(child);
            }
        }
        return true;
    }

    @Override
    public boolean update(@NonNull T t) {
        String title = dao.table().title();
        Assert.notBlank(t.getName(), "【】名称不能为空", title);
        Assert.isFalse(t.getName().contains(treeNameSeparator), "【】名称中不能有【{}】符号", title, treeNameSeparator);
        var db = super.get(t.getId());
        fixTree(t);
        if (!db.getName().equals(t.getName()) && !t.getIsLeaf()) {
            fixTree(t, db.getNamesPath());
        }
        return super.update(t);
    }

    @Override
    public boolean hasChildren(long id) {
        return has(q().eq(TreeNode::getPid, id));
    }

    @Override
    public @Nullable T getParent(long id) {
        var pid = super.get(TreeNode::getPid, id);
        return getNullable(q().eq(pid));
    }

    @Override
    public @NotNull T getRoot(long id) {
        var t = super.get(id);
        var idsPaths = t.getIdsPaths();
        var rootId = idsPaths.getFirst();
        return get(rootId);
    }


    @Override
    public boolean isRoot(long id) {
        var pid = super.get(TreeNode::getPid, id);
        return Objects.equals(pid, rootId);
    }

    @Override
    public boolean isLeaf(long id) {
        var isLeaf = super.get(TreeNode::getIsLeaf, id);
        return Objects.equals(isLeaf, true);
    }

    @Override
    public @NotNull List<T> listPathsById(long id) {
        var t = super.get(id);
        var idsPath = t.getIdsPaths();
        return list(q().in(TreeNode::getId, idsPath).orderBy(TreeNode::getSort));
    }

    @Override
    public @NotNull List<T> listTrees(long treeId, @NotNull Query<T> q) {
        var ls = list(q.eq(TreeNode::getTreeId, treeId).orderBy(TreeNode::getSort));
        return TreeUtil.listToTrees(ls, rootId);
    }

    public @NotNull List<T> listRoots(@NotNull Query<T> q) {
        return list(q.eq(TreeNode::getPid, rootId).orderBy(TreeNode::getSort));
    }

    @Override
    public @NotNull List<T> listByLevel(long treeId, int level, @NotNull Query<T> q) {
        Assert.isTrue(level > 0, "level 必须大于 0");
        return list(q().eq(TreeNode::getTreeId, treeId).eq(TreeNode::getLevel, level).orderBy(TreeNode::getSort));
    }

    @Override
    public @NotNull List<T> listDescendants(long id, @NotNull Query<T> q) {
        var idsPath = super.get(TreeNode::getIdsPath, id);
        return list(q.startsWith(TreeNode::getIdsPath, idsPath + treeNameSeparator).orderBy(TreeNode::getSort));
    }

    @Override
    public @NotNull List<T> listSelfAndDescendants(long id, @NotNull Query<T> q) {
        var idsPath = super.get(TreeNode::getIdsPath, id);
        return list(q.eq(id).or().startsWith(TreeNode::getIdsPath, idsPath + treeNameSeparator).orderBy(TreeNode::getSort));
    }

    /**
     * name pid id
     * 中国  0   1
     * 湖南  1   11
     * 长沙  11  111
     *
     * @param id 1
     * @return [湖南]
     */
    @Override
    public @NotNull List<T> listChildren(long id, @NotNull Query<T> q) {
        return list(q.eq(TreeNode::getPid, id).orderBy(TreeNode::getSort));
    }

    /**
     * name pid id
     * 中国  0   1
     * 湖南  1   11
     * 长沙  11  111
     *
     * @param id 1
     * @return [中国, 湖南]
     */
    @Override
    public @NotNull List<T> listSelfAndChildren(long id, @NotNull Query<T> q) {
        return list(q.eq(id).or().eq(TreeNode::getPid, id).orderBy(TreeNode::getSort));
    }

    @Override
    public boolean delete(@NonNull Long id) {
        Assert.isFalse(hasChildren(id), "【{}】存在子节点。请先将所有子节点删除后再操作", id);
        var parent = getParent(id);
        if (parent != null) {
            long count = count(q().eq(TreeNode::getPid, parent.getId()));
            if (count == 1L) {
                parent.setIsLeaf(true);
                super.update(parent);
            }
        }
        return super.delete(id);
    }
}
